﻿// Copyright 2012 Stuart Turner
// Licensed under the Apache License, Version 2.0 [http://www.apache.org/licenses/LICENSE-2.0]

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace Complete.Data.Linq.Expressions
{
	public class AssignmentInformation
	{
		public Expression Declaration { get; set; }
		public Expression MembershipPath { get; set; }
	}

	public abstract partial class TableValueExpression
	{
		internal class ColumnExtractor : CompleteExpressionVisitor
		{
			private List<ColumnExpression> columns = new List<ColumnExpression>();
			private List<AssignmentInformation> assignments = new List<AssignmentInformation>();
			private Expression membershipPath;
			private string alias;

			public IList<ColumnExpression> Columns { get { return columns.AsReadOnly(); } }
			public IList<AssignmentInformation> Assignments { get { return assignments.Where(a => a.MembershipPath != null).ToList().AsReadOnly(); } }

			private ColumnExtractor(string alias)
			{
				this.alias = alias;
			}

			public static ColumnExtractor ExtractColumns(TableValueExpression se, LambdaExpression expr)
			{
				var ce = new ColumnExtractor(se.Alias);
				ce.membershipPath = Expression.Parameter(se.Projection.ReturnType, "x");
				ce.Visit(expr);

				if (se.Projection.Body is ColumnExpression)
				{
					var column = se.Projection.Body as ColumnExpression;
					ce.assignments.Add(new AssignmentInformation()
					{
						Declaration = column,
						MembershipPath = ce.membershipPath,
					});
				}

				return ce;
			}

			protected override NewExpression VisitNew(NewExpression nex)
			{
				nex = DoVisitNew(nex);

				assignments.Add(new AssignmentInformation()
				{
					Declaration = nex,
					MembershipPath = membershipPath,
				});
				return nex;
			}

			private NewExpression DoVisitNew(NewExpression nex)
			{
				if (nex.Members != null)
					foreach (var x in nex.Arguments.Zip(nex.Members, (a, m) => new { Argument = a, Member = m, }))
					{
						var oldMembership = membershipPath;
						membershipPath = Expression.MakeMemberAccess(oldMembership, x.Member);
						this.Visit(x.Argument);
						membershipPath = oldMembership;
					}
				else if (nex.Arguments.Count != 0)
					throw new NotSupportedException("Not sure what to do here yet.");

				return nex;
			}

			protected override Expression VisitMemberInit(MemberInitExpression init)
			{
				DoVisitNew(init.NewExpression);
				foreach (var x in init.Bindings)
				{
					var oldMembership = membershipPath;
					membershipPath = Expression.MakeMemberAccess(oldMembership, x.Member);
					this.VisitBinding(x);
					membershipPath = oldMembership;
				}

				assignments.Add(new AssignmentInformation()
				{
					Declaration = init,
					MembershipPath = membershipPath,
				});

				return init;
			}

			private MemberExpression BuildMembershipPath(string name)
			{
				var type = membershipPath.Type;
				var property = (MemberInfo)type.GetProperty(name);
				if (property == null)
				{
					property = type.GetField(name);
					if (property == null)
						throw new ArgumentOutOfRangeException(string.Format("Cannot find member '{0}' in type '{1}'.", name, type));
				}

				return MakeMemberAccess(membershipPath, property);
			}

			protected override Expression VisitColumn(ColumnExpression column)
			{
				var columnName = column.Name;
				var index = 1;
				while (columns.Find(x => x.Name == columnName) != null)
					columnName = string.Format("{0}{1}", column.Name, ++index);

				column.Alias = alias;
				column.Ordinal = columns.Count;
				column.Name = columnName;

				column.MembershipPath = membershipPath;

				columns.Add(column);
				return column;
			}
		}
	}
}